#include "networkbot.h"

#define START_PORT 7070
#define NETWORK_PROTOCOL "1.0"

#define  NET_CMD_HELO   "LBW "NETWORK_PROTOCOL" Player"
#define  NET_CMD_SHOOT  "SHOOT"
#define  NET_END_CHAR   "\n"
#define  NET_ANS_OK     "OK"
#define  NET_ANS_ERR    "ERROR"

#define TO_STR(s)        QString("%1").arg(s)
#define LBW_STR(s)       s.toUtf8()

// --------------------------------------------------------------------------------
NetworkBot::NetworkBot()
    : ThinkerBot("NetworkBot")
{
    mSession = NULL;
    mBotId   = 0;

    connect(&mService, SIGNAL(newConnection()), this, SLOT(newSession()));
    initService();
}

// --------------------------------------------------------------------------------
void NetworkBot::handleStateChange()
{
    switch (state()) {
    case StActive : {
        if (sendShoot())
            Player::handleStateChange();
        else
            ThinkerBot::handleStateChange();
    }
    }
}

// --------------------------------------------------------------------------------
void NetworkBot::newSession()
{
    while (mService.hasPendingConnections()) {
        QTcpSocket *s = mService.nextPendingConnection();
        if (!mSession) {
            mSession = s;
            initSession();
        } else
            s->deleteLater();
    }
}

// --------------------------------------------------------------------------------
void NetworkBot::newCommand()
{
    if (sender() != mSession)
        return;
    mSessionData += mSession->readAll();
    qDebug() << mSessionData;
    if (mSessionData.endsWith(NET_END_CHAR)) {
        QList<QByteArray> parts = mSessionData.trimmed().split(' ');
        mSessionData.clear();

        if (parts[0] == NET_CMD_SHOOT && parts.count() == 3) {
            cmdShoot(parts[1].toInt(), parts[2].toInt());
            return;
        }
        sendCommand(NET_ANS_ERR);
    }
}

// --------------------------------------------------------------------------------
void NetworkBot::sessionClosed()
{
    if (mSession && mSession->state() != QAbstractSocket::ConnectedState) {
        mSession->deleteLater();
        mSession = NULL;
        if (state() == StActive)
            shoot();
    }
}

// --------------------------------------------------------------------------------
void NetworkBot::initService()
{
    if (mSession) {
        mSession->deleteLater();
        mSession = NULL;
    }
    if (mService.isListening())
        mService.close();

    int nextPort = START_PORT;
    while (!mService.listen(QHostAddress::Any,nextPort))
        nextPort++;
    mBotId = START_PORT - nextPort + 1;
    qDebug() << "NetworkBot listening to" << nextPort << mBotId;
}

// --------------------------------------------------------------------------------
void NetworkBot::initSession()
{
    Q_ASSERT(mSession);
    mSessionData.clear();
    connect(mSession,SIGNAL(readyRead()), this, SLOT(newCommand()));
    connect(mSession, SIGNAL(disconnected()), this, SLOT(sessionClosed()));
}

// --------------------------------------------------------------------------------
bool NetworkBot::sendShoot()
{
    Q_ASSERT(tank());
    QStringList cmd;
    cmd << NET_CMD_SHOOT;
    cmd << "POS"    << TO_STR(tank()->pos().x());
    cmd << "HEALTH" << TO_STR(tank()->health());
    cmd << "TANKS"  << TO_STR(enemyCount());
    for (int i=0; i<enemyCount(); i++) {
        cmd << TO_STR(enemyPos(i).x());
    }

    cmd << "GOODIES"  << TO_STR(ammoBoxCount());
    for (int i=0; i<ammoBoxCount(); i++) {
        cmd << TO_STR(ammoBoxPos(i).x());
    }

    return sendCommand(cmd.join(" "));
}

// --------------------------------------------------------------------------------
bool NetworkBot::sendCommand(const QString &cmd)
{
    if (!mSession || !mSession->state() == QAbstractSocket::ConnectedState)
        return false;
    mSession->write(LBW_STR(cmd));
    mSession->write(NET_END_CHAR);
    return true;
}

// --------------------------------------------------------------------------------
void NetworkBot::cmdShoot(int force, int angle)
{
    if (state() != StActive) {
        sendCommand(NET_ANS_ERR);
        return;
    }
    Q_ASSERT(tank());

    tank()->setForce(force);
    tank()->setAngle(angle);
    shoot();
    sendCommand(NET_ANS_OK);
}
